package nachos.threads;

import java.util.TreeMap;

import nachos.machine.*;

/**
 * Uses the hardware timer to provide preemption, and to allow threads to sleep
 * until a certain time.
 */
public class Alarm {
    /**
     * Allocate a new Alarm. Set the machine's timer interrupt handler to this
     * alarm's callback.
     *
     * <p><b>Note</b>: Nachos will not function correctly with more than one
     * alarm.
     */
    public Alarm() {
		Machine.timer().setInterruptHandler(new Runnable() {
			public void run() { timerInterrupt(); }
		    }); 
    }

    /**
     * The timer interrupt handler. This is called by the machine's timer
     * periodically (approximately every 500 clock ticks). Causes the current
     * thread to yield, forcing a context switch if there is another thread
     * that should be run.
     */
    public void timerInterrupt() {
    	KThread.yield();
    	long currentTime = Machine.timer().getTime();
    	while (waitingQueue.floorKey(currentTime) != null){
    		long t = waitingQueue.floorKey(currentTime);
    		KThread  k = (KThread) waitingQueue.get(t);
    		waitingQueue.remove(t);
    		k.ready();
    	}
    }

    /**
     * Put the current thread to sleep for at least <i>x</i> ticks,
     * waking it up in the timer interrupt handler. The thread must be
     * woken up (placed in the scheduler ready set) during the first timer
     * interrupt where
     *
     * <p><blockquote>
     * (current time) >= (WaitUntil called time)+(x)
     * </blockquote>
     *
     * @param	x	the minimum number of clock ticks to wait.
     *
     * @see	nachos.machine.Timer#getTime()
     */
    public void waitUntil(long x) {
		long wakeTime = Machine.timer().getTime() + x;
		waitingQueue.put(wakeTime, KThread.currentThread());
		
		//edited by Syl 2012.3.9
		boolean intStat = Machine.interrupt().disable();
		Lib.debug('t', "Alarm.waitUntil : " + KThread.currentThread().getName() + " start sleep, time = " + Machine.timer().getTime());
		KThread.sleep();
		Lib.debug('t', "Alarm.waitUntil : " + KThread.currentThread().getName() + " wake, time = " + Machine.timer().getTime());
		Machine.interrupt().restore(intStat);
    }
    
    private TreeMap<Long, KThread> waitingQueue = new TreeMap<Long, KThread>();
    
    //test class
    private static class AlarmTest implements Runnable{
		private AlarmTest(long _waitTime, String _name){
			waitTime = _waitTime;
			name = _name;
		}
    	public void run() {
    		printTime();
    		System.out.println("Thread " + name + " call waitUntil(" + waitTime + ").....");
    		ThreadedKernel.alarm.waitUntil(waitTime);
    		printTime();
    		System.out.println("Thread " + name + " finished waiting!");
    		
		}
		long waitTime;
		String name;  
		private void printTime(){
			System.out.print("current time = " + Machine.timer().getTime() + "  ");
			return;
		}
    }
    
    public static void selfTest(){
    	//Thread A wait for 5000 ticks 
    	KThread a = new KThread(new AlarmTest(5000, "A"));
    	a.setName("A");
    	a.fork();
    	
    	//main thread wait for 1000 ticks
    	ThreadedKernel.alarm.waitUntil(1000);
    	
    	//Thread B wait for 3000 ticks 
    	KThread b = new KThread(new AlarmTest(3000, "B"));
    	b.setName("B");
    	b.fork();

    	//main thread wait for 1000 ticks
    	ThreadedKernel.alarm.waitUntil(1000);
    	
    	//Thread C wait for 1000 ticks 
    	KThread c = new KThread(new AlarmTest(1000, "C"));
    	c.setName("C");
    	c.fork();
    	
    	//main thread wait for 4000 ticks
    	System.out.print("current time = " + Machine.timer().getTime() + "  ");
    	System.out.println("main call waitIntil(4000)....");
    	ThreadedKernel.alarm.waitUntil(4000);
    	
    	System.out.print("current time = " + Machine.timer().getTime() + "  ");
		System.out.println("main finished waiting!");
    	
    	return;
    }
    
}
